/**
 *  ADTS Header manipulation
 *
 *
 *
 *
 *
 */

/**
 * @code
 Structure
 AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
 Header consists of 7 or 9 bytes (without or with CRC).
 Letter  Length (bits)   Description
 A   12  syncword 0xFFF, all bits must be 1
 B   1   MPEG Version: 0 for MPEG-4, 1 for MPEG-2
 C   2   Layer: always 0
 D   1   protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC
 E   2   profile, the MPEG-4 Audio Object Type minus 1
 F   4  MPEG-4 Sampling Frequency Index (15 is forbidden)
 G   1   private stream, set to 0 when encoding, ignore when decoding
 H   3  MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE)
 I   1   originality, set to 0 when encoding, ignore when decoding
 J   1   home, set to 0 when encoding, ignore when decoding
 K   1   copyrighted stream, set to 0 when encoding, ignore when decoding
 L   1   copyright start, set to 0 when encoding, ignore when decoding
 M   13  frame length, this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame)
 O   11  Buffer fullness
 P   2   Number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame
 Q   16  CRC if protection absent is 0 ****THIS IS NOT PRESENT******
 */

#define _BSD_SOURCE
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define UNUSED __attribute__ ((unused))

typedef enum{
        ADTS_FRAMESIZE,
        ADTS_CHANNELCONFIG,
        ADTS_SAMPLERATE,
        ADTS_END,
}adts_n;

typedef struct{
    int shift; /* Shift left from the END Of the header */
    int width; /* Number of bits */
}adts_attribs_s;


/* This header for audio has 48000 Hz encoded into it
 * The size is found in bytes 3,2 and 1 with byte 0 being
 * the last byte. 
 * There is an extra byte at the front!!! */
static unsigned char UNUSED headerADTS[8] = {
    0x00,
    0xFF,   /* <--------- First Byte to appear in header */
    0xF9,
    0x4C,
    0x80,
    0x2F,
    0x9F,
    0xFC,
};


const adts_attribs_s adts_attribs[]={
    [ADTS_FRAMESIZE]        = {13,13},
    [ADTS_CHANNELCONFIG]    = {30,3},
    [ADTS_SAMPLERATE]       = {34,4},
    [ADTS_END]              = {0,0},
};


static FILE* infile;
static unsigned long long adts_header;


/* Function Declarations */
int adts_set(unsigned char *buffer, unsigned int size, unsigned int channels);
void header_print(unsigned long long *header);
int adts_putvalue(unsigned long long *ptr_header, adts_n attrib, int value);
int adts_getvalue(unsigned long long ptr_header, adts_n attrib, int* value);


/*
 * Need to pass in a buffer which can take at least 7 chars
 */
int main(int argc, char *argv[]){

    int res;
    int frame_size;

    if(argc != 2){
        printf("Too few/many args...\n");
        exit(0);
    }

    if(NULL == (infile=fopen(argv[1],"rw"))){
        printf("Can't open file....\n");
        exit(0);
    }

    adts_header = 0;

    if(1 != fread ( (char*)(&adts_header) + 1,7,1,infile)){
        printf("Can't read from file....\n");
        exit(0);
    }

    //fseek(infile,frame_size - 7,SEEK_CUR);

    header_print(&adts_header);

    adts_getvalue(htobe64(adts_header),ADTS_FRAMESIZE,&res);
    printf("Frame size: %dbytes.\n",res);
        
    adts_getvalue(htobe64(adts_header),ADTS_CHANNELCONFIG,&res);
    printf("Channels: %d.\n",res);

    adts_getvalue(htobe64(adts_header),ADTS_SAMPLERATE,&res);
    printf("Samples Rate Code: %d.\n",res);
    
    fclose(infile);

    return 0;

}

/**
 * @brief ...
 *
 * @param buffer ...
 * @param size ...
 * @param channels ...
 * @return int
 *
 * @remarks HEADER SIZE IS EXPECTED TO BE INCLUDED IN THE SIZE
 **/
int adts_set(unsigned char *buffer, unsigned int size, unsigned int channels){

    /* This code will assume LITTLE ENDIAN */
    unsigned long long *adts_header;
    unsigned long long be_header;
    unsigned long long new_header;
    int count;
	int UNUSED x;
    unsigned char* new_char, *new_ptr;

    if (buffer == NULL){
        printf("**NULL buffer passed to adts_set_size.\n");
        return -1;
    }

    /* Grab a ref to the header */
    adts_header = (unsigned long long*)headerADTS;

    /* Convert to big endian so the bit shifting works */
    be_header = htobe64(*adts_header);

    adts_putvalue(&be_header, ADTS_FRAMESIZE,size);

    adts_putvalue(&be_header, ADTS_CHANNELCONFIG,channels);

    /* Set samplerate to 44100 */
    adts_putvalue(&be_header, ADTS_SAMPLERATE, 4);

    /* Transfer back to little endian */
    new_header = be64toh(be_header);

    new_char = (unsigned char*)&new_header;
    new_ptr = (unsigned char *)buffer;

    /* Now copy in the correct header */
    for (count=1; count<8; count++)
        *new_ptr++ = new_char[count];

#ifdef DEBUG
    printf("ADTS:  Size 0x%X.... ",size);

    for (x=0;x<20;x++)
        printf("%X",buffer[x]);

    printf("\n");
#endif


    return 0;

}


void header_print(unsigned long long *header){

    unsigned char* ptr_header;
    int x;

    ptr_header = (unsigned char*)header;

    printf("ADTS:  ");

    for (x=0;x<8;x++)
        printf("%X",ptr_header[x]);

    printf("\n");

}


/**
 * @brief Put value inside an ADTS header...
 *
 * @param ptr_header ...
 * @param attrib ...
 * @param value ...
 * @return int
 **/
int adts_putvalue(unsigned long long *ptr_header, adts_n attrib, int value){

    unsigned long long lvalue;
    unsigned long long mask;

    mask = (0xFFFFFFFFFFFFFFFFULL >> (64-adts_attribs[attrib].width));

    lvalue = (unsigned long long)value;

    /* Clear invalid bits */
    lvalue &= mask;
//
    /* Shift value into place */
    lvalue <<= adts_attribs[attrib].shift;

    /* Clear out the header */
    *ptr_header &= ~(mask << adts_attribs[attrib].shift);

    /* Add in the value */
    *ptr_header |= lvalue;

    /* And we're done. */
    return 0;

}


/**
 * @brief Get value inside an ADTS header...
 *
 * @param ptr_header ...
 * @param attrib ...
 * @param value ...
 * @return int
 **/
int adts_getvalue(unsigned long long ptr_header, adts_n attrib, int* value){

    unsigned long long lvalue;
    unsigned long long mask;

    mask = (0xFFFFFFFFFFFFFFFFULL >> (64-adts_attribs[attrib].width));
    mask <<= adts_attribs[attrib].shift;

    lvalue = ptr_header & mask;

    lvalue >>= adts_attribs[attrib].shift;

    *value = (int)lvalue;

    /* And we're done. */
    return 0;

}




